/*
 * Copyright (c) 2012-2013, Charles Santos da Silva <silva.charlessantos@gmail.com> -
 * scharlessantos.org
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 * 1. Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE
 */

package org.scharlessantos.hermes;

import org.scharlessantos.hermes.logger.HFileLogger;
import org.scharlessantos.hermes.logger.HSysoutLogger;
import org.scharlessantos.hermes.writer.HFilter;
import org.scharlessantos.hermes.writer.HLevel;
import org.scharlessantos.hermes.writer.HWriter;

import java.util.ArrayList;
import java.util.List;

/**
 * Hermes is free open-source Java Logger.<br>
 * It receive this name because Hermes was the messenger of Olympians gods, so,
 * anybody better than Hermes to send system messages to programmer.<br>
 * 
 * <br>
 * Hermes is licensed by new BSD License. A notice of license can be saw below<br>
 * <br>
 * <i>
 * Copyright (c) 2012-2013, Charles Santos da Silva <silva.charlessantos@gmail.com> -
 * scharlessantos.org
 * All rights reserved.<br>
 * <br>
 * Redistribution and use in source and binary forms, with or without<br>
 * modification, are permitted provided that the following conditions are met:<br>
 * <br>
 * 1. Redistributions of source code must retain the above copyright notice,<br>
 * this list of conditions and the following disclaimer.<br>
 * 2. Redistributions in binary form must reproduce the above copyright<br>
 * notice, this list of conditions and the following disclaimer in the<br>
 * documentation and/or other materials provided with the distribution.<br>
 * <br>
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"<br>
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE<br>
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE<br>
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE<br>
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR<br>
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF<br>
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS<br>
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN<br>
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)<br>
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE<br>
 * POSSIBILITY OF SUCH DAMAGE<br>
 * </i>
 * 
 * @author Charles Santos da Silva
 * 
 */
public class Hermes {
	
	private static List<HLogger> loggers     = new ArrayList<HLogger>();
	private static int           extraParent = 0;
	
	static {
		addLogger(new HLogger(HSysoutLogger.getInstance(), null));
		addLogger(new HLogger(HFileLogger.getInstance(), null));
	}
	
	/**
	 * Defines who element that stacktrace is owner of log. By default, extraParent is zero, and
	 * owner of log is who call Hermes.info or Hermes.debug or Hermes.xxx where xxx not is
	 * logExParent. <br>
	 * See the line below: <br>
	 * <br>
	 * <i>[INFO ] ganesh.Starter.main(<b>Starter.java:38</b>)@Thread 1:: Starting Ganesh
	 * Server</i><br>
	 * <br>
	 * The bold is the class/line who stack trace element that Hermes gets. If you increase
	 * extraParent, who by default is zero, the method/class who called whom calls Hermes will
	 * appear in log.<br>
	 * <br>
	 * By default, the parent count is 3.<br>
	 * To explain, see the code below. <br>
	 * 
	 * <br>
	 * 01 import org.scharlessantos.hermes.Hermes;<br>
	 * 02<br>
	 * 03 public class Sample {<br>
	 * 04<br>
	 * 05 &nbsp&nbsp&nbsp&nbsp public static void main(String[] args) {<br>
	 * 06 &nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp sample();<br>
	 * 07 &nbsp&nbsp&nbsp&nbsp }<br>
	 * 08<br>
	 * 09 &nbsp&nbsp&nbsp&nbsp public static void sample() {<br>
	 * 10 &nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp Hermes.info("Hello word!");<br>
	 * 11 &nbsp&nbsp&nbsp&nbsp }<br>
	 * 12<br>
	 * 13 }<br>
	 * <br>
	 * <br>
	 * The explain parent stack trace element works for code above:<br>
	 * <table border=1>
	 * <tr>
	 * <th>Array Position</th>
	 * <th>extraParentValue</th>
	 * <th>Log Line</th>
	 * </tr>
	 * <tr>
	 * <td>0</td>
	 * <td>-3</td>
	 * <td>[INFO ] java.lang.Thread.getStackTrace(Thread.java:1567)@1: Hello word!</td>
	 * </tr>
	 * <tr>
	 * <td>1</td>
	 * <td>-2</td>
	 * <td>[INFO ] org.scharlessantos.hermes.Hermes.logExParent(Hermes.java:221)@Thread 1:: Hello
	 * world!</td>
	 * </tr>
	 * </tr>
	 * <tr>
	 * <td>2</td>
	 * <td>-1</td>
	 * <td>[INFO ] org.scharlessantos.hermes.Hermes.info(Hermes.java:243)@Thread 1::Hello world!</td>
	 * </tr>
	 * </tr>
	 * <tr>
	 * <td>3</td>
	 * <td>0 (default)</td>
	 * <td>[INFO ] Sample.sample(Sample.java:10)@1: Hello word!</td>
	 * </tr>
	 * </tr>
	 * <tr>
	 * <td>4</td>
	 * <td>1</td>
	 * <td>[INFO ] Sample.main(Sample.java:6)@1: Hello word!</td>
	 * </tr>
	 * </table>
	 * 
	 * @param count
	 */
	public static void setExtraParentCount(int count) {
		extraParent = count;
	}
	
	/**
	 * Add a new Hermes Writer implementation
	 * 
	 * @param writer writer who write logs
	 * @param filter filter that define who messages and exceptions can be log
	 * @see HWriter
	 * @see HFilter
	 */
	public static void addLogger(HWriter writer, HFilter filter) {
		HLogger logger = new HLogger(writer, filter);
		addLogger(logger);
	}
	
	private static void addLogger(HLogger logger) {
		synchronized (loggers) {
			loggers.add(logger);
		}
	}
	
	/**
	 * Remove a writer/filter from Hermes
	 * 
	 * @param writer Writer to remove
	 * @param filter Filter to remove
	 */
	public static void removeLogger(HWriter writer, HFilter filter) {
		HLogger logger = new HLogger(writer, filter);
		loggers.remove(logger);
	}
	
	/**
	 * Removes the Hermes Default Console Writer from Hermes
	 * 
	 * @see HSysoutLogger
	 */
	public static void removeDefaultConsoleLogger() {
		removeLogger(HSysoutLogger.getInstance(), null);
	}
	
	/**
	 * Removes the default Hermes File Logger from Hermes
	 * 
	 * @see HFileLogger
	 */
	public static void removeDefaultFileLogger() {
		removeLogger(HFileLogger.getInstance(), null);
	}
	
	/**
	 * Log a message
	 * 
	 * @param level Level to log message
	 * @param source message log origin
	 * @param message message to log
	 */
	public static final void logEx(HLevel level, StackTraceElement source, String message) {
		for (HLogger logger : loggers)
			logger.log(level, source, message);
	}
	
	/**
	 * Log a exception
	 * 
	 * @param level Level to log exception
	 * @param source exception log origin
	 * @param exception exception to log
	 */
	public static final void logEx(HLevel level, StackTraceElement source, Throwable exception) {
		for (HLogger logger : loggers)
			logger.log(level, source, exception);
	}
	
	/**
	 * Log a message
	 * 
	 * @param level Level to log message
	 * @param parentCount define that stack trace element will be the message log origin. more at
	 *            {@link #setExtraParentCount(int)}
	 * @param message message to log
	 */
	public static final void logExParent(HLevel level, int parentCount, String message) {
		for (HLogger logger : loggers)
			logger.log(level, Thread.currentThread().getStackTrace()[parentCount + 2 + extraParent], message);
	}
	
	/**
	 * Log a exception
	 * 
	 * @param level Level to log exception
	 * @param parentCount define that stack trace element will be the exception log origin. more at
	 *            {@link #setExtraParentCount(int)}
	 * @param exception exception to log
	 */
	public static final void logExParent(HLevel level, int parentCount, Throwable exception) {
		for (HLogger logger : loggers)
			logger.log(level, Thread.currentThread().getStackTrace()[parentCount + 2 + extraParent], exception);
	}
	
	/**
	 * Log a message with {@link HLevel#INFO}
	 * 
	 * @param message message to log
	 */
	public static final void info(String message) {
		logExParent(HLevel.INFO, 1, message);
	}
	
	/**
	 * Log a message using {@link String#format(String, Object...)} with {@link HLevel#INFO}
	 * 
	 * @param message message to log
	 * @param args args to format
	 */
	public static final void info(String message, Object... args) {
		logExParent(HLevel.INFO, 1, String.format(message, args));
	}
	
	/**
	 * Log a exception with {@link HLevel#INFO}
	 * 
	 * @param exception exception to log
	 */
	public static final void info(Throwable exception) {
		logExParent(HLevel.INFO, 1, exception);
	}
	
	/**
	 * Log a message using {@link String#format(String, Object...)} with {@link HLevel#DEBUG}
	 * 
	 * @param message message to log
	 */
	public static final void debug(String message) {
		logExParent(HLevel.DEBUG, 1, message);
	}
	
	/**
	 * Log a message using {@link String#format(String, Object...)} with {@link HLevel#DEBUG}
	 * 
	 * @param message message to log
	 * @param args args to format
	 */
	public static final void debug(String message, Object... args) {
		logExParent(HLevel.DEBUG, 1, String.format(message, args));
	}
	
	/**
	 * Log a exception with {@link HLevel#DEBUG}
	 * 
	 * @param exception exception to log
	 */
	public static final void debug(Throwable exception) {
		logExParent(HLevel.DEBUG, 1, exception);
	}
	
	/**
	 * Log a message with {@link HLevel#WARN}
	 * 
	 * @param message message to log
	 */
	public static final void warn(String message) {
		logExParent(HLevel.WARN, 1, message);
	}
	
	/**
	 * Log a message using {@link String#format(String, Object...)} with {@link HLevel#WARN}
	 * 
	 * @param message message to log
	 * @param args args to format
	 */
	public static final void warn(String message, Object... args) {
		logExParent(HLevel.WARN, 1, String.format(message, args));
	}
	
	/**
	 * Log a exception with {@link HLevel#WARN}
	 * 
	 * @param exception exception to log
	 */
	public static final void warn(Throwable exception) {
		logExParent(HLevel.WARN, 1, exception);
	}
	
	/**
	 * Log a message using with {@link HLevel#ERROR}
	 * 
	 * @param message message to log
	 */
	public static final void error(String message) {
		logExParent(HLevel.ERROR, 1, message);
	}
	
	/**
	 * Log a message using {@link String#format(String, Object...)} with {@link HLevel#ERROR}
	 * 
	 * @param message message to log
	 * @param args args to format
	 */
	public static final void error(String message, Object... args) {
		logExParent(HLevel.ERROR, 1, String.format(message, args));
	}
	
	/**
	 * Log a exception with {@link HLevel#ERROR}
	 * 
	 * @param exception exception to log
	 */
	public static final void error(Throwable exception) {
		logExParent(HLevel.ERROR, 1, exception);
	}
	
	/**
	 * Log a message using with {@link HLevel#FATAL}
	 * 
	 * @param message message to log
	 */
	public static final void fatal(String message) {
		logExParent(HLevel.FATAL, 1, message);
	}
	
	/**
	 * Log a message using {@link String#format(String, Object...)} with {@link HLevel#FATAL}
	 * 
	 * @param message message to log
	 * @param args args to format
	 */
	public static final void fatal(String message, Object... args) {
		logExParent(HLevel.FATAL, 1, String.format(message, args));
	}
	
	/**
	 * Log a exception with {@link HLevel#FATAL}
	 * 
	 * @param exception exception to log
	 */
	public static final void fatal(Throwable exception) {
		logExParent(HLevel.FATAL, 1, exception);
	}
	
}
